﻿using System;
using System.Diagnostics.Contracts;

namespace Beaker.OpenCube.Render
{
	public struct Normal
		: IEquatable<Normal>
	{
		public static readonly Normal Zero = new Normal( 0d, 0d, 0d );
		public static readonly Normal One = new Normal( 1d, 1d, 1d );

		private readonly double _x;
		private readonly double _y;
		private readonly double _z;

		public Normal( double x, double y, double z )
		{
			_x = x;
			_y = y;
			_z = z;
		}

		#region Value

		/// <summary>
		/// Gets the X offset of the normal
		/// </summary>
		[Pure]
		public double X
		{
			get { return _x; }
		}

		/// <summary>
		/// Gets the Y offset of the normal
		/// </summary>
		[Pure]
		public double Y
		{
			get { return _y; }
		}

		/// <summary>
		/// Gets the Z offset of the normal
		/// </summary>
		[Pure]
		public double Z
		{
			get { return _z; }
		}

		#endregion
		#region Magnitude

		[Pure]
		private double Magnitude()
		{
			return Math.Sqrt( _x * _x + _y * _y + _z * _z );
		}

		private static double Magnitude( Normal n )
		{
			return n.Magnitude();
		}

		#endregion

		#region Equality

		[Pure]
		public override bool Equals( object obj )
		{
			if( ReferenceEquals( null, obj ) ) return false;
			if( ReferenceEquals( this, obj ) ) return true;
			if( obj.GetType() != this.GetType() ) return false;
			return Equals( (Vector)obj );
		}

		[Pure]
		public bool Equals( Normal other )
		{
			if( ReferenceEquals( null, other ) ) return false;
			if( ReferenceEquals( this, other ) ) return true;
			return _x.Equals( other._x ) && _y.Equals( other._y ) && _z.Equals( other._z );
		}

		public static bool operator ==( Normal left, Normal right )
		{
			return Equals( left, right );
		}

		public static bool operator !=( Normal left, Normal right )
		{
			return !Equals( left, right );
		}

		[Pure]
		public override int GetHashCode()
		{
			unchecked
			{
				int hashCode = _x.GetHashCode();
				hashCode = ( hashCode * 397 ) ^ _y.GetHashCode();
				hashCode = ( hashCode * 397 ) ^ _z.GetHashCode();
				return hashCode;
			}
		}
		#endregion
		#region Negate

		public Normal Negate()
		{
			return
				new Normal(
					-_x,
					-_y,
					-_z
				);
		}

		public static Normal operator -( Normal n )
		{
			return n.Negate();
		}

		#endregion
		#region Addition

		[Pure]
		public Normal Add( Normal other )
		{
			return
				new Normal(
					_x + other._x,
					_y + other._y,
					_z + other._z
				);
		}

		public static Normal operator +( Normal left, Normal right )
		{
			return left.Add( right );
		}

		#endregion
		#region Multiplication (normal and Dot)

		[Pure]
		public Normal Multiply( double value )
		{
			return
				new Normal(
					_x * value,
					_y * value,
					_z * value
				);
		}

		public static Normal Multiply( Normal left, double right )
		{
			return left.Multiply( right );
		}

		public static Normal operator *( Normal left, double right )
		{
			return left.Multiply( right );
		}


		[Pure]
		public double DotProduct( Vector vector )
		{
			return vector.X * _x + vector.Y * _y + vector.Z * _z;
		}

		public static double DotProduct( Normal left, Vector right )
		{
			return left.DotProduct( right );
		}

		public static double operator *( Normal left, Vector right )
		{
			return left.DotProduct( right );
		}

		#endregion
		#region Convert

		public static explicit operator Vector( Normal normal )
		{
			return 
				new Vector(
					normal.X,
					normal.Y,
					normal.Z
				);
		}

		#endregion
	}
}
